今天就要來製作我們的第一個第三方庫啦!
目標是模仿[Day 21] 使用第三方庫--以json為例,製作出一個Config.cmake檔,讓find_package找到! 但並不包含任何有用資訊,單純作為讓使用者確定是否有安裝好依賴項。
定義:使用configure_package_config_file前必須包含的助手函數。(固定寫法)
定義:建立一個Config文件,讓使用者可以利用生成的文件來使用該庫。
原理和[Day 18] 版本號與宏使用 xxx.in文件生成其他文件很像,都是將 xxx.in中的某些元素替換成CMakeLists.txt中的變數來生成一個文件。
configure_package_config_file(<input> <output>
INSTALL_DESTINATION <path>
[PATH_VARS <var1> <var2> ... <varN>]
)
input : 輸入文件,通常名為 xxxConfig.cmake.in
output : 輸出文件,通常名為 xxxConfig.cmake,find_package就是透過這個文件來找到第三方庫的。
INSTALL_DESTINATION : 安裝路徑,通常位於 usr/local/lib/cmake 中(在這裡也可以發現前兩天裝的 Opencv)
或者是 usr/local/share/cmake 中(在這裡可以發現nlohmann_json),這些都是 find_package 預設會搜尋的路徑,在 Day 19有介紹過。
PATH_VARS <var1> <var2>... : 用CMake中的 var1、var2...變數替換xxxConfig.cmake.in文件中的 @var1@、@var2@...。
cmake_minimum_required(VERSION 3.22)
# 根據自己的cmake版本來設定最小版本,要小於當前版本
project(MathFunctions VERSION 1.0.0 LANGUAGES CXX)
# 設定專案名稱為MathFunctions,版本為1.0.0,且此項目使用C++
add_library(mysqrt SHARED src/mysqrt.cpp)
# 將mysqrt.cpp編譯成庫
target_include_directories(mysqrt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 設定庫的頭文件路徑
set_target_properties(mysqrt PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR})
# 設定庫的版本號,實做版本和專案版本相同,界面版本和主要版本相同
include(GNUInstallDirs)
# 引入 GNUInstallDirs 變數
install(FILES include/mysqrt.h DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR})
# 安裝庫到 /usr/local/include
install(TARGETS mysqrt DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
# 安裝頭文件到 /usr/local/bin
include(CMakePackageConfigHelpers)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/Config.cmake.in
${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)
install(FILES ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake DESTINATION lib/cmake/${PROJECT_NAME})
# 讀入 ${PROJECT_SOURCE_DIR}/Config.cmake.in 文件
# 生成 ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 文件
# 並且將生成出的 Config.cmake 安裝到 /usr/local/lib/cmake/${PROJECT_NAME},讓find_package能夠在預設的搜索路徑找到
@PACKAGE_INIT@
check_required_components(${PROJECT_NAME})
$ cd build
$ cmake ..
$ make
$ sudo make install
kai@esoc:~/2023_iT_CMake/Day22/MathFunctions/build$ sudo make install
[100%] Built target mysqrt
Install the project...
-- Install configuration: ""
-- Up-to-date: /usr/local/include/mysqrt.h
-- Up-to-date: /usr/local/lib/libmysqrt.so.1.0.0
-- Up-to-date: /usr/local/lib/libmysqrt.so.1
-- Up-to-date: /usr/local/lib/libmysqrt.so
-- Up-to-date: /usr/local/lib/cmake/MathFunctions/MathFunctionsConfig.cmake <----生成的Config.cmake檔⭐
cmake_minimum_required(VERSION 3.22)
# 根據自己的cmake版本來設定最小版本,要小於當前版本
project(cmake_totorial VERSION 1.0.0 LANGUAGES CXX)
# 設定專案名稱為cmake_totorial,版本為1.0.0,且此項目使用C++
find_package(MathFunctions REQUIRED)
# 尋找MathFunctionsConfig.cmake
add_executable(main src/main.cpp)
# 將main.cpp編譯成可執行文件main
target_link_libraries(main PUBLIC mysqrt)
# 將mysqrt連結到執行檔main
configure_file(version.h.in version.h)
# 將version.h.in輸出成version.h
target_include_directories(main PUBLIC "${PROJECT_BINARY_DIR}")
# 將PROJECT_BINARY_DIR加入main的include路徑,使main可以include到 build/version.h
$ cd build
$ make
$ ./main 10
這個時候會發現跳出一個錯誤訊息
./main: error while loading shared libraries: libmysqrt.so.1: cannot open shared object file: No such file or directory
我們使用 ldd 指令來查看鍊結到的庫訊息,此時會發現鍊結器找不到該庫,
原因是鍊結器的路徑 並沒有包含 usr/local/lib。
ldd main
kai@esoc:~/2023_iT_CMake/Day22/Main/build$ ldd main
linux-vdso.so.1 (0x00007fff7efc4000)
libmysqrt.so.1 => not found
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fcc13a00000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fcc13daf000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc13600000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fcc13cc8000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcc13dfb000)
所以現在將庫所在的路徑加入鍊結器的搜尋路徑中
$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
接下來確認是否有正確加入
$ echo $LD_LIBRARY_PATH
kai@esoc:~/2023_iT_CMake/Day22/Main/build$ echo $LD_LIBRARY_PATH
/usr/local/lib::/usr/local/cuda/lib64
這個時候再執行就可以發現程式可以順利執行了。
$ ./main 10
是不是覺得這樣很麻煩?
不僅需要知道這個庫需要鍊結什麼庫(target_link_libraries(main PUBLIC mysqrt)),還要知道庫放在哪裡($LD_LIBRARY_PATH),因此明天會介紹如何像OpenCV 一樣,使用者只要像以下這樣包含定義好的頭文件路徑與庫路徑就好,不用了解具體的路徑就可以使用。
include_directories(${OpenCV_INCLUDE_DIRS})
# 將 OpenCV 的 include 路徑加入到專案中
add_executable(main src/main.cpp)
# 將 main.cpp 編譯成可執行文件 main
target_link_libraries(main ${OpenCV_LIBS})
# 將 OpenCV 的 library 路徑加入到專案中
P.S. 為什麼 nlohmann_json 並沒有提供find_package任何鍊結庫的資訊,卻不用像今天一樣要加入LD_LIBRARY_PATH才能執行呢?
因為 nlohmann_json 這個第三方庫其安裝的檔案都是 .hpp ,沒有像一般的第三方庫提供的是標頭檔與庫文件。